昨天我們看過了
DB::table('user')->get();
裡面的 table()
函數是怎麼取得 \Illuminate\Database\Query\Builder
物件。今天我們來看看 get()
底層的實作,以及怎麼取出一個 Laravel Collection。
public function get($columns = ['*'])
{
$items = collect($this->onceWithColumns(Arr::wrap($columns), function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
return $this->applyAfterQueryCallbacks(
isset($this->groupLimit) ? $this->withoutGroupLimitKeys($items) : $items
);
}
首先我們看到 onceWithColumns()
/**
* Execute the given callback while selecting the given columns.
*
* After running the callback, the columns are reset to the original value.
*
* @param array $columns
* @param callable $callback
* @return mixed
*/
protected function onceWithColumns($columns, $callback)
{
$original = $this->columns;
if (is_null($original)) {
$this->columns = $columns;
}
$result = $callback();
$this->columns = $original;
return $result;
}
這邊會整理出所有的 column
資訊,我們這邊沒有特別指定,所以是預設的 ['*']
接著我們看到 processSelect()
public function processSelect(Builder $query, $results)
{
return $results;
}
沒什麼可看的,看起來實際解析的邏輯不在這邊。那麼就應該在 runSelect()
裡面了!
/**
* Run the query as a "select" statement against the connection. * * @return array
*/protected function runSelect()
{
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
}
根據函數名稱來看,這邊開始就會深入到取得 SQL 語法的地方了
我們先來看看 toSql()
/**
* Get the SQL representation of the query.
*
* @return string
*/
public function toSql()
{
$this->applyBeforeQueryCallbacks();
return $this->grammar->compileSelect($this);
}
這邊我們可以看到,這邊的語法會使用 $this->grammar
進行分析。我們往下看 $this->grammar->compileSelect($this)
/**
* Compile a select query into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
public function compileSelect(Builder $query)
{
if (($query->unions || $query->havings) && $query->aggregate) {
return $this->compileUnionAggregate($query);
}
// If a "group limit" is in place, we will need to compile the SQL to use a
// different syntax. This primarily supports limits on eager loads using
// Eloquent. We'll also set the columns if they have not been defined.
if (isset($query->groupLimit)) {
if (is_null($query->columns)) {
$query->columns = ['*'];
}
return $this->compileGroupLimit($query);
}
// If the query does not have any columns set, we'll set the columns to the
// * character to just get all of the columns from the database. Then we
// can build the query and concatenate all the pieces together as one.
$original = $query->columns;
if (is_null($query->columns)) {
$query->columns = ['*'];
}
// To compile the query, we'll spin through each component of the query and
// see if that component exists. If it does we'll just call the compiler
// function for the component which is responsible for making the SQL.
$sql = trim($this->concatenate(
$this->compileComponents($query))
);
if ($query->unions) {
$sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
}
$query->columns = $original;
return $sql;
}
這邊我們沒有 $query->unions
、$query->havings
、$query->aggregate
,所以會一路往下,到 $query->columns = ['*']
接著我們看看 compileComponents()
/**
* Compile the components necessary for a select clause. * * @param \Illuminate\Database\Query\Builder $query
* @return array
*/protected function compileComponents(Builder $query)
{
$sql = [];
foreach ($this->selectComponents as $component) {
if (isset($query->$component)) {
$method = 'compile'.ucfirst($component);
$sql[$component] = $this->$method($query, $query->$component);
}
}
return $sql;
}
這段會根據 $query->$component
是否存在,呼叫對應的函數。
不過這邊選用了對函數命名做呼叫的方式進行篩選。
由於我們有設置 $query->columns
和 $query->from
,所以我們會呼叫到 compileColumns()
和 compileFrom()
兩個函數。
這兩個函數會提供我們所需的 sql 片段
/**
* Compile the "select *" portion of the query. * * @param \Illuminate\Database\Query\Builder $query
* @param array $columns
* @return string|null
*/protected function compileColumns(Builder $query, $columns)
{
// If the query is actually performing an aggregating select, we will let that
// compiler handle the building of the select clauses, as it will need some // more syntax that is best handled by that function to keep things neat. if (! is_null($query->aggregate)) {
return;
}
if ($query->distinct) {
$select = 'select distinct ';
} else {
$select = 'select ';
}
return $select.$this->columnize($columns);
}
/**
* Compile the "from" portion of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $table
* @return string
*/
protected function compileFrom(Builder $query, $table)
{
return 'from '.$this->wrapTable($table);
}
取出片段之後,會在 $this->concatenate()
裡面進行組合
/**
* Concatenate an array of segments, removing empties. * * @param array $segments
* @return string
*/protected function concatenate($segments)
{
return implode(' ', array_filter($segments, function ($value) {
return (string) $value !== '';
}));
}
組合之後,再經過 trim()
,就可以生成完整的 SQL 語句了!
我們繼續看 $this->connection->select()
/**
* Run a select statement against the database. * * @param string $query
* @param array $bindings
* @param bool $useReadPdo
* @return array
*/public function select($query, $bindings = [], $useReadPdo = true)
{
return $this->run($query, $bindings, function ($query, $bindings) use ($useReadPdo) {
if ($this->pretending()) {
return [];
}
// For select statements, we'll simply execute the query and return an array
// of the database result set. Each element in the array will be a single // row from the database table, and will either be an array or objects. $statement = $this->prepared(
$this->getPdoForSelect($useReadPdo)->prepare($query)
);
$this->bindValues($statement, $this->prepareBindings($bindings));
$statement->execute();
return $statement->fetchAll();
});
}
run()
的實作則是
/**
* Run a SQL statement and log its execution context. * * @param string $query
* @param array $bindings
* @param \Closure $callback
* @return mixed
* * @throws \Illuminate\Database\QueryException
*/protected function run($query, $bindings, Closure $callback)
{
foreach ($this->beforeExecutingCallbacks as $beforeExecutingCallback) {
$beforeExecutingCallback($query, $bindings, $this);
}
$this->reconnectIfMissingConnection();
$start = microtime(true);
// Here we will run this query. If an exception occurs we'll determine if it was
// caused by a connection that has been lost. If that is the cause, we'll try // to re-establish connection and re-run the query with a fresh connection. try {
$result = $this->runQueryCallback($query, $bindings, $callback);
} catch (QueryException $e) {
$result = $this->handleQueryException(
$e, $query, $bindings, $callback
);
}
// Once we have run the query we will calculate the time that it took to run and
// then log the query, bindings, and execution time so we will report them on // the event that the developer needs them. We'll log time in milliseconds. $this->logQuery(
$query, $bindings, $this->getElapsedTime($start)
);
return $result;
}
到這邊,我們就看過了在 DB::table('user')->get();
裡面,Laravel 是怎麼建立出 SQL Query,並且利用 PDO 物件對資料庫進行存取的。